package com.atguigu.gmall.search.service.impl;

import com.atguigu.gmall.search.pojo.Goods;
import com.atguigu.gmall.search.pojo.SearchParamVO;
import com.atguigu.gmall.search.pojo.SearchResponseAttrVO;
import com.atguigu.gmall.search.pojo.SearchResponseVO;
import com.atguigu.gmall.search.service.SearchService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
import org.apache.commons.lang3.StringUtils;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.*;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Service
public class SearchServiceImpl implements SearchService {

    @Autowired
    private RestHighLevelClient restHighLevelClient;

    private static final Gson gson=new Gson();

    private static final ObjectMapper OBJECT_MAPPER=new ObjectMapper();
    @Override
    public SearchResponseVO search(SearchParamVO searchParamVO) throws IOException {
        //构建Dsl语句
        SearchRequest searchRequest=this.buildQueryDsl(searchParamVO);
        SearchResponse response = this.restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        System.out.println(response);//输出响应的结果集

        SearchResponseVO responseVO = this.parseSearchResult(response);
        responseVO.setPageSize(searchParamVO.getPageSize());
        responseVO.setPageNum(searchParamVO.getPageNum());
        return responseVO;

    }


    private SearchResponseVO parseSearchResult(SearchResponse response) throws JsonProcessingException {
        SearchResponseVO responseVO=new SearchResponseVO();

        //获取总记录数
        SearchHits hits = response.getHits();
        responseVO.setTotal(hits.getTotalHits());
        //解析品牌的集合结果集
        SearchResponseAttrVO brand=new SearchResponseAttrVO();
        brand.setName("品牌");
        //获取品牌的聚合结果集
        Aggregations aggregations = response.getAggregations();
        Map<String, Aggregation> aggregationMap = aggregations.asMap();

        //品牌
        ParsedLongTerms brandIdAgg = (ParsedLongTerms) aggregationMap.get("brandIdAgg");
        List<String> brandValues = brandIdAgg.getBuckets().stream().map(bucket -> {

                    Map<String,String> map=new HashMap<>();
                    //获取品牌id
                    map.put("id",bucket.getKeyAsString());
                    //获取品牌名称
                    Map<String, Aggregation> brandIdSubMap = bucket.getAggregations().asMap();//从聚合里面获取
                    ParsedStringTerms  brandNameAgg=(ParsedStringTerms)brandIdSubMap.get("brandNameAgg");
                    //获取桶
                    String brandName = brandNameAgg.getBuckets().get(0).getKeyAsString();
                    map.put("name",brandName);
                    //return gson.toJson(map);
                    try {
                        return OBJECT_MAPPER.writeValueAsString(map);
                    } catch (JsonProcessingException e) {
                        e.printStackTrace();
                    }
                    return null;
                }
                ).collect(Collectors.toList());


        brand.setValue(brandValues);
        responseVO.setBrand(brand);

        //分类
        ParsedLongTerms categoryIdAgg = (ParsedLongTerms) aggregationMap.get("categoryIdAgg");
        List<String> catelogValues = categoryIdAgg.getBuckets().stream().map(bucket -> {

                    Map<String,String> map=new HashMap<>();
                    //获取分类id
                    map.put("id",bucket.getKeyAsString());
                    //获取分类名称
                    Map<String, Aggregation> categoryIdSubMap = bucket.getAggregations().asMap();//从聚合里面获取
            ParsedStringTerms categoryNameAgg = (ParsedStringTerms) categoryIdSubMap.get("categoryNameAgg");
            //获取桶
                    String categoryName = categoryNameAgg.getBuckets().get(0).getKeyAsString();
                    map.put("name",categoryName);
                    //return gson.toJson(map);
                    try {
                        return OBJECT_MAPPER.writeValueAsString(map);
                    } catch (JsonProcessingException e) {
                        e.printStackTrace();
                    }
                    return null;
                }
        ).collect(Collectors.toList());
        SearchResponseAttrVO category=new SearchResponseAttrVO();
        category.setName("分类");
        category.setValue(catelogValues);
        responseVO.setCatelog(category);


        //商品
        SearchHit[] subHits = hits.getHits();
        List<Goods> goodsList=new ArrayList<>();//因为需要的是List,所以需要转换为List
        for (SearchHit subHit:subHits) {
            Goods goods = OBJECT_MAPPER.readValue(subHit.getSourceAsString(), new TypeReference<Goods>() {
            });//反序列化为goods对象
            //高亮显示
            goods.setTitle(subHit.getHighlightFields().get("title").getFragments()[0].toString());
            goodsList.add(goods);
        }
        responseVO.setProducts(goodsList);//set实际就是看定义的类型

        //规格参数的聚合
        //获取嵌套聚合对象
        ParsedNested attr_agg = (ParsedNested) aggregationMap.get("attr_agg");
        //规格参数id聚合对象
        ParsedLongTerms attrIdAgg = (ParsedLongTerms) attr_agg.getAggregations().get("attrIdAgg");
        List<Terms.Bucket> buckets =(List<Terms.Bucket>) attrIdAgg.getBuckets();//获取桶
        if(!CollectionUtils.isEmpty(buckets)){
            List<SearchResponseAttrVO> searchResponseAttrVOS = buckets.stream().map(bucket -> {
                SearchResponseAttrVO responseAttrVO = new SearchResponseAttrVO();
                //设置规格参数的id
                responseAttrVO.setProductAttributeId(bucket.getKeyAsNumber().longValue());
                //设置规格参数名
                List<? extends Terms.Bucket> nameBucket = ((ParsedStringTerms) (bucket.getAggregations().get("attrNameAgg"))).getBuckets();//获取子聚合
                responseAttrVO.setName(nameBucket.get(0).getKeyAsString());
                //设置规格参数值
                List<? extends Terms.Bucket> valueBuckets = ((ParsedStringTerms) (bucket.getAggregations().get("attrValueAgg"))).getBuckets();//获取子聚合
                List<String> value = valueBuckets.stream().map(Terms.Bucket::getKeyAsString).collect(Collectors.toList());
                responseAttrVO.setValue(value);
                return responseAttrVO;
            }).collect(Collectors.toList());
            responseVO.setAttrs(searchResponseAttrVOS);
        }



        return responseVO;
    }
    private SearchRequest buildQueryDsl(SearchParamVO searchParamVO){

//        String keyword= searchParamVO.getKeyword();//从参数中获取关键字
//        if(StringUtils.isEmpty(keyword)){
//            return null;//如果查询关键字为空，则返回null,结束方法
//        }
        //2、初始化SourceBUilder,查询条件的构建器
        SearchSourceBuilder sourceBuilder=new SearchSourceBuilder();
        //3查询及过滤条件
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        //3、1构建查询条件
        if (StringUtils.isNotBlank(searchParamVO.getKeyword())) {
            boolQueryBuilder.must(QueryBuilders.matchQuery("title", searchParamVO.getKeyword()).operator(Operator.AND));
        }
        //3、2构建品牌过滤条件
        String[] brand = searchParamVO.getBrand();
        if (brand != null && brand.length > 0) {
            boolQueryBuilder.filter(QueryBuilders.termsQuery("brandId",brand));
        }
        //3、3构建分类的过滤条件
        // 分类过滤
        String[] catelog3 = searchParamVO.getCatelog3();
        if (catelog3 != null && catelog3.length > 0){
            boolQueryBuilder.filter(QueryBuilders.termsQuery("categoryId", catelog3));
        }
        //3.4构建属性规格嵌套过滤
        String[] props = searchParamVO.getProps();
        if (props != null && props.length > 0) {

            for(String prop:props){

                //以：进行分割，分割后应该是2个元素：1-attrId  2-attrValue(以-分割的字符串)
                String[] attr = StringUtils.split(prop, ":");
                if (attr == null || attr.length != 2){
                    continue;
                }
                //以-分割处理处AttrValues
                String[] attrValues=StringUtils.split(attr[1],"-");

                BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
                //中间参数又是boolean查询，所以还需要构建
                BoolQueryBuilder subBoolQuery = QueryBuilders.boolQuery();
                subBoolQuery.must(QueryBuilders.termQuery("attrValueList.attrId",attr[0]));//must里面又是两个词条查询
                subBoolQuery.must(QueryBuilders.termsQuery("attrValueList.attrValue",attrValues));

                boolQuery.must(QueryBuilders.nestedQuery("attrValueList",subBoolQuery, ScoreMode.None));
                boolQueryBuilder.filter(boolQuery);
            }
        }

        //3.5价格区间过滤
        RangeQueryBuilder rangeQueryBuilder=QueryBuilders.rangeQuery("price");
        Integer priceFrom = searchParamVO.getPriceFrom();
        Integer priceTo = searchParamVO.getPriceTo();
        if(priceFrom!=null){
            rangeQueryBuilder.gte(priceFrom);
        }
        if(priceTo!=null){
            rangeQueryBuilder.lte(priceTo);
        }
        boolQueryBuilder.filter(rangeQueryBuilder);
        //2、2查询构建写进构建器
        sourceBuilder.query(boolQueryBuilder);

        //4、1构建分页
        Integer pageNum = searchParamVO.getPageNum();
        Integer pageSize = searchParamVO.getPageSize();
        sourceBuilder.from((pageNum-1)*pageSize);
        sourceBuilder.size(pageSize);

        //5、1构建排序
        String order = searchParamVO.getOrder();
        if(!StringUtils.isEmpty(order)){
            //不为空才处理
            String[] split = StringUtils.split(order, ":");
            if(split!=null&&split.length==2){
                String filed=null;
                switch (split[0]){
                    case "1":
                        filed="sale";
                        break;
                    case "2":
                        filed="price";
                        break;

                }
                sourceBuilder.sort(filed,StringUtils.equals("asc",split[1])? SortOrder.ASC:SortOrder.DESC);//升序还是降序
            }

        }

        //6、1构建高亮
        sourceBuilder.highlighter(new HighlightBuilder().field("title").preTags("<em>").postTags("</em>"));

        //7构建聚合
        //7\1品牌聚合
        sourceBuilder.aggregation(AggregationBuilders.terms("brandIdAgg").field("brandId").
                subAggregation(AggregationBuilders.terms("brandNameAgg").field("brandName")));
        //7\2分类聚合
        sourceBuilder.aggregation(AggregationBuilders.terms("categoryIdAgg").field("categoryId").
                subAggregation(AggregationBuilders.terms("categoryNameAgg").field("categoryName")));
        //7\3搜索的规格属性聚合
        sourceBuilder.aggregation(AggregationBuilders.nested("attr_agg","attrValueList")
                .subAggregation(AggregationBuilders.terms("attrIdAgg").field("attrValueList.attrId")
                .subAggregation(AggregationBuilders.terms("attrNameAgg").field("attrValueList.attrName"))
                .subAggregation(AggregationBuilders.terms("attrValueAgg").field("attrValueList.attrValue"))));



        //System.out.println(sourceBuilder.toString());//输出dsl语句

        //8结果集过滤，只显示有用的字段
        sourceBuilder.fetchSource(new String[]{"skuId","pic","title","price"},null);
        //1、因为返回的是searchRequest,所以先声明，查询参数
        SearchRequest searchRequest=new SearchRequest("goods");//索引库
        searchRequest.types("info");
        searchRequest.source(sourceBuilder);//2、还因为需要sourceBuild,所以在构建

        return searchRequest;
    }

}
